/****************************************************************************\
*                                                                            *
*  Copyright 2008 The CAnt2 Project (http://code.google.com/p/cant2)         *
*                                                                            *
*  Licensed under the Apache License, Version 2.0 (the "License");           *
*  you may not use this file except in compliance with the License.          *
*  You may obtain a copy of the License at                                   *
*                                                                            *
*      http://www.apache.org/licenses/LICENSE-2.0                            *
*                                                                            *
*  Unless required by applicable law or agreed to in writing, software       *
*  distributed under the License is distributed on an "AS IS" BASIS,         *
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
*  See the License for the specific language governing permissions and       *
*  limitations under the License.                                            *
*                                                                            *
\****************************************************************************/

#include "MiniXmlDOM.h"
#include "StringPool.h"

#include <stack>
#include <vector>

namespace MiniXmlDOM {

    StringWithSource::StringWithSource ( const wchar_t* value )
        : handle ( StringPool::Insert ( value ) )
        , sourceLine ( ~0 )
        , sourceColumn ( ~0 )
    {
    }

    void StringWithSource::Swap ( StringWithSource& other )
    {
        std::swap ( handle, other.handle );
        std::swap ( sourceLine, other.sourceLine );
        std::swap ( sourceColumn, other.sourceColumn );
    }

    Attribute::Attribute ( const StringWithSource& name, const StringWithSource& value )
        : name ( name )
        , value ( value )
    {
    }

    void Attribute::Swap ( Attribute& other )
    {
        name.Swap ( other.name );
        value.Swap ( other.value );
        nextAttribute.Swap ( other.nextAttribute );
    }

    Element::Element ( const StringWithSource& name )
        : name ( name )
    {
    }

    void Element::Swap ( Element& other )
    {
        name.Swap ( other.name );
        firstAttribute.Swap ( other.firstAttribute );
        firstChild.Swap ( other.firstChild );
    }

    Document::Document()
        : fileNameHandle ( StringPool::Insert ( std::wstring() ) )
    {
    }

    void Document::Swap ( Document& other )
    {
        fileNameHandle.Swap ( other.fileNameHandle );
        firstChild.Swap ( other.firstChild );
    }

/****************************************************************************/

    class DocumentFactory::Details
    {
    public:
        Document document;
        std::stack < Element*, std::vector < Element* > > elementStack;
    };

/****************************************************************************/

    namespace {

        void ReverseAttributeChain( ValuePtr<Attribute>& head )
        {
            ValuePtr<Attribute> tail;
            while ( !head.IsEmpty() )
            {
                tail.Swap ( head );
                head.Swap ( tail->nextAttribute );
            }
            tail.Swap ( head );
        }

        void ReverseElementChain ( ValuePtr<Element>& head )
        {
            ValuePtr<Element> tail;
            while ( !head.IsEmpty() )
            {
                tail.Swap ( head );
                head.Swap ( tail->nextSibling );
            }
            tail.Swap ( head );
        }

    }

    DocumentFactory::DocumentFactory()
        : pimpl ( new Details )
    {
    }

    DocumentFactory::~DocumentFactory()
    {
    }

    void DocumentFactory::Get ( Document& document )
    {
        document.Swap ( pimpl->document );
    }

    void DocumentFactory::ElementBegin ( const wchar_t* name )
    {
        ValuePtr<Element> newElementPtr ( new Element ( StringWithSource ( name ) ) );
        Element* newElement = newElementPtr.Get();

        if ( pimpl->elementStack.empty() )
        {
            if ( !pimpl->document.firstChild.IsEmpty() )
            {
                throw std::runtime_error ( "The root element cannot be followed by another element." );
            }
            pimpl->document.firstChild.Swap ( newElementPtr );
        } else
        {
            Element* element = pimpl->elementStack.top();
            newElementPtr->nextSibling.Swap ( element->firstChild );
            element->firstChild.Swap ( newElementPtr );
        }

        pimpl->elementStack.push ( newElement );
    }

    void DocumentFactory::ElementEnd   ( const wchar_t* name )
    {
        if ( pimpl->elementStack.empty() )
        {
            throw std::runtime_error ( "Attempting to end an element when the element stack is empty!" );
        }

        Element* element = pimpl->elementStack.top();

        if ( element->name.handle != StringPool::Insert ( name ) )
        {
            throw std::runtime_error ( "Attempting to end an element that does not match the top of the element stack!" );
        }

        ReverseElementChain ( element->firstChild );

        pimpl->elementStack.pop();
    }

    void DocumentFactory::Attribute    ( const wchar_t* name, const wchar_t* value )
    {
        if ( pimpl->elementStack.empty() )
        {
            throw std::runtime_error ( "Attempting to set on attribute on element when the element stack is empty!" );
        }

        Element* element = pimpl->elementStack.top();
        ValuePtr<MiniXmlDOM::Attribute> newAttributePtr ( new MiniXmlDOM::Attribute ( StringWithSource ( name ), StringWithSource ( value ) ) );
        newAttributePtr->nextAttribute.Swap ( element->firstAttribute );
        element->firstAttribute.Swap ( newAttributePtr );
    }

    void DocumentFactory::ElementEndAttributes ( const wchar_t* name )
    {
        if ( pimpl->elementStack.empty() )
        {
            throw std::runtime_error ( "Attempting to end an element's attributes when the element stack is empty!" );
        }

        Element* element = pimpl->elementStack.top();

        if ( element->name.handle != StringPool::Insert ( name ) )
        {
            throw std::runtime_error ( "Attempting to end an element's attributes when that does not match the top of the element stack!" );
        }

        ReverseAttributeChain ( element->firstAttribute );
    }

    void DocumentFactory::EndOfFile    ()
    {
        if ( !pimpl->elementStack.empty() )
        {
            throw std::runtime_error ( "Attempting to end the file with a non-empty element stack" );
        }
    }

}

